'/
' GUI Library - version 1.14
' Written by Anton Berlin, 2010.
' 
' =========================================================================
' 
' This program is free software; you can redistribute it and/or modify
' it under the terms of the GNU Lesser General Public License as published
' by the Free Software Foundation; either version 3 of the License.
' 
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
' GNU General Public License for more details.
' 
' You should have received a copy of the GNU Lesser General Public License
' along with this program; if not, write to the Free Software
' Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
'/

superstrict

import BRL.LinkedList
import BRL.FreeTypeFont
import BRL.Map
import BRL.Threads
import BRL.Reflection
import BRL.PolledInput
import BRL.GLMax2D
import Pub.Glew

import "GUI_InputThread.bmx"
import "GUI_Surface.bmx"
import "GUI_Event.bmx"

import "../errorutil/errorutil.bmx"

'// -------------------------------------------------------------------------------------------------------------
'// Pointer to the currently active surface (i.e. the surface being drawn and used at the moment).
'// -------------------------------------------------------------------------------------------------------------
global g_activeSurface:GUI_Surface;

type GUI extends Base

	'// -------------------------------------------------------------------------------------------------------------
	'// Initialize the graphical user interface.
	'// -------------------------------------------------------------------------------------------------------------
	function error(str:string)
	
		'// Put your error code in here.
		errorutil.error("Error: " + str);
		GCCollect();
		end;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Initialize the graphical user interface.
	'// -------------------------------------------------------------------------------------------------------------
	function init(resX:int, resY:int)
	
		reset();
		
		if (resX < 4) ..
			error("Invalid resX value.");
		if (resY < 3) ..
			error("Invalid resY value.");
		
		m_resX = resX;
		m_resY = resY;
		
		RabMT.launchThread("__GUI__input_thread", GUI_InputThread.inputThread, null);
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Deinitialize the graphical user interface.
	'// -------------------------------------------------------------------------------------------------------------
	function deinit()
	
		GUI_InputThread.m_endInputThread = true;
		RabMT.waitThread("__GUI__input_thread");
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Deinitialize and reset the graphical user interface.
	'// -------------------------------------------------------------------------------------------------------------
	function reset()
	
		m_resX = 4;
		m_resY = 3;
		
		m_surfaceList = new TList;
		m_surfaceListReversed = new TList;
		m_surfaceMap = new TMap;
		
		g_activeSurface = null;
		
		m_event = null;
		m_hookedObject = null;
		
		drawutil._oldFont = null;
		drawutil._oldAlpha = 0.0;
		drawutil._oldR = 0;
		drawutil._oldG = 0;
		drawutil._oldB = 0;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Update the graphical user interface.
	'// -------------------------------------------------------------------------------------------------------------
	function update()
	
		local currentKeyStroke:short;
		
		if (not m_deactive) ..
			currentKeyStroke = GUI_InputThread.getCurrentKey(); ..
		else ..
			currentKeyStroke = 0;
		
		'// We use the reversed lists here because we want to access the top layer and top gadgets first.
		for local layer:GUI_Layer = eachin g_activeSurface.m_layerListReversed
		
			'// Skip layers that are not active.
			if (not layer.m_active) ..
				continue;
			
			local exitLoop:byte = false;
			
			for local gadget:GUI_Gadget = eachin layer.m_gadgetListReversed
			
				'// Skip gadgets that are not active.
				if (not gadget.m_active) ..
					continue;
				
				'// Update buttons, forms and lists.
				select (gadget.m_type)
				
					case GADGET_BUTTON
						GUI_Button(gadget).update();
					case GADGET_FORM
						GUI_Form(gadget).update(currentKeyStroke);
					case GADGET_CHECKBOX
						GUI_Checkbox(gadget).update();
					case GADGET_LIST
						GUI_List(gadget).update();
					case GADGET_MENU
						GUI_Menu(gadget).update();
					case GADGET_SLIDER
						GUI_Slider(gadget).update();
				
				end select
				
				'// Update input.
				'exitLoop = updateInput(gadget, currentKeyStroke);
				if (not m_deactive) ..
					updateInput(gadget, currentKeyStroke);
				
				'if (exitLoop) ..
				'	continue;
			
			next
			
			if (exitLoop) ..
				exit;
		
		next
		
		if (m_deactive) ..
			return;
		
		'// If no event has been emitted, return. Otherwise, proceed to try and execute the current event action.
		if (not m_event) ..
			return;
		
		'// Determine which action should be taken.
		select (m_event.m_type)
		
			case GUI_Event.EVENT_NONE
			case GUI_Event.EVENT_INVOKE_METHOD
				'// See if the event object is corrupted.
				if (not m_event.m_methodHook) then
				
					error("Event has no method hooked to it.");
					m_event = null;
					return;
				
				else if (not m_event.m_hookedObject and not m_hookedObject) then
				
					error("No hooked object found.");
					m_event = null;
					return;
				
				end if
				
				local id:TTypeId;
				local func:TMethod;
				
				'// Get the type id of the hooked object. The hooked object is the object where the external methods
				'// that will be called by the GUI are residing.
				if (m_event.m_hookedObject) ..
					id = TTypeId.forObject(m_event.m_hookedObject); ..
				else ..
					id = TTypeId.forObject(m_hookedObject);
				
				'// Make sure the hooked object can be found.
				if (not id) then
				
					error("Hooked object couldn't be found.");
					m_event = null;
					return;
				
				end if
				
				'// Get the method to invoke, from the gadget the event was sent from.
				func = id.findMethod(m_event.m_methodHook);
				
				local hookedObj:object;
				if (m_event.m_hookedObject) ..
					hookedObj = m_event.m_hookedObject; ..
				else ..
					hookedObj = m_hookedObject;
				
				'// Remove the event.
				m_event = null;
				
				'// Invoke the method specified by the event.
				func.invoke(hookedObj, null);
			default
				error("Unknown event id.");
		
		end select
		
		'// Make sure the event is removed.
		m_event = null;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Update mouse and keyboard input.
	'// -------------------------------------------------------------------------------------------------------------
	function updateInput:byte(gadget:GUI_Gadget, currentKeyStroke:int)
	
		local result:byte = false;
		
		'// Exit loop if 'keyboardInput()' returns true.
		'result = keyboardInput(gadget, currentKeyStroke);
		
		'// Return if the mouse is not hovering anywhere over the current gadget.
		if (not gadget.mouseHovering()) then
		
			if (m_selectedMenu and m_selectedMenu.mouseHoveringOverItems()) then
			
				return mouseInput(m_selectedMenu);
			
			else
			
				if (mouseDown(1)) then
				
					select (gadget.m_type)
					
						case GADGET_MENU
							selectMenu(null);
					
					end select
				
				end if
				
				return false;
			
			end if
		
		end if
		
		'// Exit loop if 'mouseInput()' returns true.
		result = mouseInput(gadget);
		
		return result;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Handle the input received from the keyboard. Returns true if the gadget was affected by the keyboard input.
	'// -------------------------------------------------------------------------------------------------------------
	function keyboardInput:byte(gadget:GUI_Gadget, currentKeyStroke:int)
	
		local result:byte = false;
		
		'if (m_selectedForm) ..
		'	m_selectedForm.update();
		
		if (not m_selectedForm) ..
			flushKeys();
		
		return result;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Handle the input received from the mouse. Returns true if the gadget was affected by the mouse input.
	'// -------------------------------------------------------------------------------------------------------------
	function mouseInput:byte(gadget:GUI_Gadget)
	
		local result:byte = false;
		local mouseLeftPressed:byte = false;
		local mouseRightPressed:byte = false;
		
		if (mouseDown(1)) ..
			mouseLeftPressed = true; ..
		else if (mouseDown(2)) ..
			mouseRightPressed = true;
		
		if (not mouseLeftPressed and not mouseRightPressed) ..
			return false;
		
		result = true;
		
		select (gadget.m_type)
		
			case GADGET_BUTTON
				if (gadget.m_active) ..
					GUI_Button(gadget).press();
			case GADGET_CHECKBOX
				local checkbox:GUI_Checkbox = GUI_Checkbox(gadget);
				if (checkbox.m_active) then
				
					if (m_selectedCheckbox <> checkbox and checkbox.m_global) ..
						selectCheckbox(checkbox);
					if (not checkbox.m_global) ..
						checkbox.press();
				
				end if
			case GADGET_FORM
				if (gadget.m_active and m_selectedForm <> gadget) ..
					selectForm(GUI_Form(gadget));
			case GADGET_MENU
				if (gadget.m_active) then
				
					local menu:GUI_Menu = GUI_Menu(gadget);
					
					menu.press();
					
					if (menu.m_open) ..
						selectMenu(menu); ..
					else ..
						selectMenu(null);
				
				end if
			default
				result = false;
		
		end select
		
		return result;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Draw the currently selected surface.
	'// -------------------------------------------------------------------------------------------------------------
	function draw()
	
		g_activeSurface.draw();
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Return a pointer to a surface from the surface containers.
	'// -------------------------------------------------------------------------------------------------------------
	function getSurface:GUI_Surface(name:string)
	
		return GUI_Surface(m_surfaceMap.valueForKey(name));
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Add a surface to the surface containers.
	'// -------------------------------------------------------------------------------------------------------------
	function addSurface(name:string)
	
		local surface:GUI_Surface = new GUI_Surface;
		
		surface.m_name = name;
		
		m_surfaceList.addLast(surface);
		m_surfaceListReversed.addFirst(surface);
		m_surfaceMap.insert(name, surface);
		
		surface.m_surfaceListLink = m_surfaceList.lastLink();
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Select and activate a surface. Only one surface can be activated at any one time.
	'// -------------------------------------------------------------------------------------------------------------
	function selectSurface(name:string)
	
		g_activeSurface = getSurface(name);
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Set the default hooked object.
	'// -------------------------------------------------------------------------------------------------------------
	function setHookedObject(obj:object)
	
		m_hookedObject = obj;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Select a new checkbox.
	'// -------------------------------------------------------------------------------------------------------------
	function selectCheckbox(checkbox:GUI_Checkbox)
	
		for local c:GUI_Checkbox = eachin GUI_Layer(checkbox.m_layer).m_gadgetList
		
			if (c.m_global) ..
				c.m_selected = false;
			if (c = checkbox) ..
				c.m_selected = true;
		
		next
		
		m_selectedCheckbox = checkbox;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Select a new form.
	'// -------------------------------------------------------------------------------------------------------------
	function selectForm(form:GUI_Form)
	
		if (not form) then
		
			for local layer:GUI_Layer = eachin g_activeSurface.m_layerList;
			
				for local f:GUI_Form = eachin layer.m_gadgetList
				
					f.m_selected = false;
				
				next
			
			next
			
			m_selectedForm = null;
			
			return;
		
		end if
		
		local selectedAForm:byte = false;
		for local f:GUI_Form = eachin GUI_Layer(form.m_layer).m_gadgetList
		
			f.m_selected = false;
			if (f = form and not selectedAForm) then
			
				f.m_selected = true;
				selectedAForm = true;
			
			end if
		
		next
		
		m_selectedForm = form;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Select a new menu.
	'// -------------------------------------------------------------------------------------------------------------
	function selectMenu(menu:GUI_Menu)
	
		if (not menu) then
		
			for local layer:GUI_Layer = eachin g_activeSurface.m_layerList;
			
				for local m:GUI_Menu = eachin layer.m_gadgetList
				
					m.m_open = false;
					m.m_selectedMenuItem = null;
				
				next
			
			next
			
			m_selectedMenu = null;
			
			return;
		
		end if
		
		for local m:GUI_Menu = eachin GUI_Layer(menu.m_layer).m_gadgetList
		
			m.m_open = false;
			if (m = menu) ..
				m.m_open = true;
		
		next
		
		m_selectedMenu = menu;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Activates the GUI.
	'// -------------------------------------------------------------------------------------------------------------
	function activate()
	
		m_deactive = false;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Deactivates the GUI temporarily.
	'// -------------------------------------------------------------------------------------------------------------
	function deactivate()
	
		m_deactive = true;
	
	end function
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Pointer to the currently selected form.
	'// -------------------------------------------------------------------------------------------------------------
	global m_selectedForm:GUI_Form = null;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Pointer to the currently selected menu.
	'// -------------------------------------------------------------------------------------------------------------
	global m_selectedMenu:GUI_Menu = null;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Pointer to the currently selected menu item.
	'// -------------------------------------------------------------------------------------------------------------
	global m_selectedMenuItem:GUI_MenuItem = null;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Containers for surface objects.
	'// -------------------------------------------------------------------------------------------------------------
	global m_surfaceList:TList;
	global m_surfaceListReversed:TList;
	global m_surfaceMap:TMap;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// The default object where we will search for hooked methods.
	'// -------------------------------------------------------------------------------------------------------------
	global m_hookedObject:object;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Set this to true to deactivate the GUI temporarily (handy when no input should be given to the GUI, for
	'// example).
	'// -------------------------------------------------------------------------------------------------------------
	global m_deactive:byte = false;

end type

global sGUI:GUI = new GUI;
